package com.prometheus.util;

import io.prometheus.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;

/**
 * @author: Alan
 * @date: 2021/5/18 13:25
 */
public class Metric {

    private static Logger log = LoggerFactory.getLogger(Metric.class);
    public static String help = "none";

    private final static Map<String, Collector> cache = new HashMap<>();
    private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);


    /*
     *1.  name      表示指标唯一的名字，全局唯一。每个name的指标对应一张折线图
     *2.  label     指标的标签，一个指标可以拥有一个name+多个label，如统计响应时间的指标可以拥有，label为’200‘的指标，label为’500‘的指标，其name相同，label不同
     *              不同的label对应同一张折线图上的不同的折线
     *
     *              label数量不宜过多，不然一张折线图上无法画出所有label，可能导致页面加载不出来，最大数量控制在十几个以内。
     */

    /**
     * 只增不减的计数器
     * 用于监控：每分钟处理数量
     *
     * @param name       计数器名称
     * @param labelValue 计数器标签的值
     */
    public static Counter.Child counter(@NotNull String name, @NotNull String labelValue) {
        String finalName = Utils.clearNamespace(name);
        Collector collector = getCollector(finalName);

        if (collector == null) {
            collector = createAndRegister(finalName, () -> Counter.build(finalName, help).labelNames("label").create());
        }
        if (collector instanceof Counter) {
            return ((Counter) collector).labels(labelValue);
        }
        throw new IllegalStateException("collector name:" + finalName + " expect Counter,but instanceOf " + collector.getClass().getSimpleName());
    }

    /**
     * 可增可减的技术器
     * 用于监控：当前并发数
     *
     * @param name       gauge名称
     * @param labelValue label值
     * @return 返回一个指定名称 + 指定label的Gauge
     */
    public static Gauge.Child gauge(@NotNull String name, @NotNull String labelValue) {
        String finalName = Utils.clearNamespace(name);
        Collector collector = getCollector(finalName);

        if (collector == null) {
            collector = createAndRegister(finalName, () -> Gauge.build(finalName, help).labelNames("label").create());
        }
        if (collector instanceof Gauge) {
            return ((Gauge) collector).labels(labelValue);
        }
        throw new IllegalStateException("collector name:" + finalName + " expect Gauge,but instanceOf " + collector.getClass().getSimpleName());
    }

    /**
     * 注册自定义Gauge
     * 用于监控：最大连接数，可用连接数，磁盘使用率，cpu占用率，温度，积压订单数，已处理订单数
     *
     * @param name       gauge名称
     * @param labelValue label值
     * @param gauge      每次调用返回一个double类型的值
     */
    public static void registerCustomGauge(@NotNull String name, @NotNull String labelValue, @NotNull Supplier<Double> gauge) {
        String finalName = Utils.clearNamespace(name);
        Collector collector = getCollector(finalName);

        if (collector == null) {
            collector = createAndRegister(finalName, () -> CustomGauge.build(finalName, help).labelNames("label").create());
        }
        if (collector instanceof CustomGauge) {
            ((CustomGauge) collector).labels(labelValue).setSupplier(gauge);
            return;
        }
        throw new IllegalStateException("collector name:" + finalName + " expect CustomGauge,but instanceOf " + collector.getClass().getSimpleName());
    }

    /**
     * 直方图，按照时间将记录扔进对应的时间分片桶里
     * 统计5毫秒 10毫秒 25毫秒 50毫秒 75毫秒 100毫秒 250毫秒 500毫秒 750毫秒 1秒 2.5秒 5秒  7秒 10秒 内处理完成的数量
     */
    public static Histogram.Child histogram(@NotNull String name, @NotNull String labelValue) {
        String finalName = Utils.clearNamespace(name);
        Collector collector = getCollector(finalName);

        if (collector == null) {
            collector = createAndRegister(finalName, () -> Histogram.build(finalName, help)
                    .buckets(.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10)
                    .labelNames("label").create());
        }
        if (collector instanceof Histogram) {
            return ((Histogram) collector).labels(labelValue);
        }
        throw new IllegalStateException("collector name:" + finalName + " expect Histogram,but instanceOf " + collector.getClass().getSimpleName());
    }

    /**
     * 用于监控：分位数，如50%的请求在多少毫秒内，75%的请求在多少毫秒内，90%的请求在多少毫秒内，95%请求在多少毫秒内
     * 统计的原理是使用10s长度的滑动窗口，每次获取数据时，计算当前窗口内的分位数
     *
     * @param name       Summary名称
     * @param labelValue label值
     */
    public static Summary.Child summary(@NotNull String name, @NotNull String labelValue) {
        String finalName = Utils.clearNamespace(name);
        Collector collector = getCollector(finalName);

        if (collector == null) {
            collector = createAndRegister(finalName, () -> Summary.build(finalName, help)
                    .quantile(0.5, 0.01)
                    .quantile(0.75, 0.01)
                    .quantile(0.9, 0.01)
                    .quantile(0.95, 0.01)
                    .labelNames("label").create());
        }
        if (collector instanceof Summary) {
            return ((Summary) collector).labels(labelValue);
        }
        throw new IllegalStateException("collector name:" + finalName + " expect Summary,but instanceOf " + collector.getClass().getSimpleName());
    }

    private static Collector createAndRegister(String key, Supplier<Collector> supplier) {
        Lock lock = readWriteLock.writeLock();
        try {
            lock.lock();
            Collector collector = cache.get(key);
            if (collector != null) {
                return collector;
            }
            collector = supplier.get();
            cache.put(key, collector);
            collector.register();
            log.info("registry a new collector name:{} type:{}", key, collector.getClass());
            return collector;
        } finally {
            lock.unlock();
        }
    }

    private static Collector getCollector(String key) {
        Lock r = readWriteLock.readLock();
        r.lock();
        try {
            return cache.get(key);
        } finally {
            r.unlock();
        }
    }

}
